﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.SqlClient;
using System.Web.Configuration;
using System.Xml;

namespace TeamFoundation.EventBus
{
    public class InstallAction : IAction
    {
        #region IAction Members

        public void Action(HttpContext context)
        {
            bool isInstalled = WebConfigurationManager.AppSettings["isInstalled"] == "true";
            if (isInstalled)
            {
                context.Response.Write(
                    "Already installed. You can return to the <a href='Default.aspx'>Deck</a>.");
                return;
            }
            if(!context.User.IsInRole("Administrators"))
            {
                context.Response.Write(
                    "You do not have sufficient permissions to install. Please return to the <a href='Default.aspx'>Deck</a>.");
                return;
            }

            // Get the configuration data.
            string dbServer = context.Request.Params["dbServer"];
            string dbName = context.Request.Params["dbName"];
            string tfServer = context.Request.Params["tfServer"];
            string tfAccount = context.Request.Params["tfAccount"];
            bool dbCreate = context.Request.Params["dbCreate"] == "true";
            bool dbTables = context.Request.Params["dbTables"] == "true";

            // Start the installation.
            Install(context, dbServer, dbName, tfServer, tfAccount, dbCreate, dbTables);
        }

        #endregion

        #region Install
        private void Install(HttpContext context, string dbServer, string dbName,
            string tfServer, string tfAccount, bool dbCreate, bool dbTables)
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = dbServer;
            builder.InitialCatalog = "master";
            builder.IntegratedSecurity = true;
            string connectionString = builder.ToString();

            context.Response.Write("--- Database ---<br />");
            context.Response.Flush();
            bool success = CreateDatabase(context, connectionString, dbServer, dbName, tfServer, tfAccount, dbCreate, dbTables);

            if (success)
            {
                builder.InitialCatalog = dbName;
                builder.MultipleActiveResultSets = true;
                builder.Pooling = true;
                connectionString = builder.ToString();

                context.Response.Write("--- Configuration ---<br />");
                context.Response.Flush();
                success = Configure(context, connectionString, dbServer, dbName, tfServer, tfAccount, dbCreate, dbTables);
            }

            context.Response.Write("<br />");
            if (success)
            {
                context.Response.Write(
                    "Installation succeeded. You can now return to the <a href='Default.aspx'>Deck</a>.");
            }
            else
            {
                context.Response.Write("<br />");
                context.Response.Write(
                    "Installation failed. Please press the back button in your browser and correct the issues.");
            }
        }
        #endregion

        #region Configure
        private bool Configure(HttpContext context, string connectionString,
            string dbServer, string dbName, string tfServer, string tfAccount,
            bool dbCreate, bool dbTables)
        {
            try
            {
                string configPath = context.Server.MapPath("~/web.config");

                context.Response.Write(string.Format("Updating {0}...<br />", configPath));
                context.Response.Flush();

                XmlDocument doc = new XmlDocument();
                doc.Load(configPath);

                // Update it.
                const string format = "metadata=res://*/Model.Database.csdl|res://*/Model.Database.ssdl|res://*/Model.Database.msl;provider=System.Data.SqlClient;provider connection string='{0}'";
                string conString = string.Format(format, connectionString);
                UpdateValue(doc, "Database Connection String", "/configuration/connectionStrings/add[@name='Database']/@connectionString", conString);
                UpdateValue(doc, "IsInstalled", "/configuration/appSettings/add[@key='isInstalled']/@value", "true");
                UpdateValue(doc, "DbServer", "/configuration/appSettings/add[@key='dbServer']/@value", dbServer);
                UpdateValue(doc, "DbName", "/configuration/appSettings/add[@key='dbName']/@value", dbName);
                UpdateValue(doc, "TfServer", "/configuration/appSettings/add[@key='tfServer']/@value", tfServer);

                doc.Save(configPath);

                return true;
            }
            catch (Exception ex)
            {
                context.Response.Write("<span style='color: red'>");
                context.Response.Write("Configuration failed: <br />");
                context.Response.Write("</span>");
                context.Response.Write(ex.ToString());
                context.Response.Flush();
                return false;
            }
        }

        private void UpdateValue(XmlDocument doc, string name, string xpath, string newValue)
        {
            XmlAttribute attr = (XmlAttribute)doc.SelectSingleNode(xpath);
            if (attr == null)
                throw new Exception(string.Format("'{0}' not found in configuration.", name));
            attr.Value = newValue;
        }
        #endregion

        #region < DB >
        private bool CreateDatabase(HttpContext context, string connectionString,
            string dbServer, string dbName, string tfServer, string tfAccount,
            bool dbCreate, bool dbTables)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();

                    using (SqlCommand command = new SqlCommand())
                    {
                        command.Connection = connection;
                        command.CommandType = System.Data.CommandType.Text;

                        #region < DB >
                        if (dbCreate)
                        {
                            context.Response.Write("Creating database... <br />");
                            context.Response.Flush();
                            // Drop the existing database.
                            command.CommandText =
        @"IF  EXISTS (SELECT name FROM sys.databases WHERE name = N'{0}')
DROP DATABASE [{0}]";
                            command.CommandText = string.Format(command.CommandText, dbName);
                            command.ExecuteNonQuery();

                            // Create the new database.
                            command.CommandText =
        @"CREATE DATABASE [{0}]";
                            command.CommandText = string.Format(command.CommandText, dbName);
                            command.ExecuteNonQuery();
                            context.Response.Write("Database created. <br />");
                            context.Response.Flush();
                        }
                        #endregion

                        #region < Use >
                        // Select the db.
                        command.CommandText = "USE [{0}]";
                        command.CommandText = string.Format(command.CommandText, dbName);
                        command.ExecuteNonQuery();
                        #endregion

                        #region < Tables >
                        // Create the tables.
                        if (dbTables)
                        {
                            context.Response.Write("Creating tables... <br />");
                            context.Response.Flush();

                            command.CommandText =
         @"IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[subscription]') AND type in (N'U'))
DROP TABLE [subscription]";
                            command.ExecuteNonQuery();

                            command.CommandText =
        @"CREATE TABLE [dbo].[subscription]
(
	[id] [bigint] IDENTITY(1,1) NOT NULL,
	[name] [nvarchar](50) NOT NULL,
	[messageQueue] [nvarchar](255) NOT NULL,
	[filter] [nvarchar](1024) NOT NULL,
	CONSTRAINT [PK_subscription] PRIMARY KEY CLUSTERED 
	( 
		[id] ASC
	)
)";
                            command.ExecuteNonQuery();

                            context.Response.Write("Tables created. <br />");
                            context.Response.Flush();
                        }
                        #endregion

                        #region < Permissions >

                        if (!string.IsNullOrEmpty(tfAccount))
                        {
                            context.Response.Write("Assigning Permissions... <br />");
                            context.Response.Flush();

                            command.CommandText =
    @"IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'{0}')
CREATE LOGIN [{0}] FROM WINDOWS WITH DEFAULT_DATABASE=[{1}]";
                            command.CommandText = string.Format(command.CommandText,
                                    tfAccount, dbName);
                            command.ExecuteNonQuery();

                            command.CommandText =
@"IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'TFSServiceAccount')
BEGIN
	CREATE USER [TFSServiceAccount] FOR LOGIN [{0}] WITH DEFAULT_SCHEMA=[dbo]
	EXEC sp_addrolemember N'db_datareader', N'TFSServiceAccount'
	EXEC sp_addrolemember N'db_datawriter', N'TFSServiceAccount'
END";
                            command.CommandText = string.Format(command.CommandText,
                                tfAccount);
                            command.ExecuteNonQuery();

                            context.Response.Write("Assignined permissions. <br />");
                            context.Response.Flush();
                        }
                        #endregion
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                context.Response.Write("<span style='color: red'>");
                context.Response.Write("Database creation failed: <br />");
                context.Response.Write("</span>");
                context.Response.Write(ex.ToString());
                context.Response.Flush();
                return false;
            }
        }
        #endregion
    }
}
